package com.lfy.kcat.content.biz.impl;

import cn.hutool.core.io.FileUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.lfy.kcat.content.biz.TencentVodService;
import com.lfy.kcat.content.domain.Dramas;
import com.lfy.kcat.content.domain.Episodes;
import com.lfy.kcat.content.mapper.DramasMapper;
import com.lfy.kcat.content.mapper.EpisodesMapper;
import com.lfy.kcat.content.vo.EpisodeVodRelation;
import com.lfy.kcat.content.vod.properties.VodProperties;
import com.lfy.kcat.content.xxl.XxlHttp;
import com.qcloud.vod.VodUploadClient;
import com.qcloud.vod.model.VodUploadRequest;
import com.qcloud.vod.model.VodUploadResponse;
import com.tencentcloudapi.vod.v20180717.VodClient;
import com.tencentcloudapi.vod.v20180717.models.*;
import com.xxl.job.core.context.XxlJobHelper;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.VodConst;
import org.dromara.common.core.utils.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.InputStream;
import java.net.HttpCookie;
import java.net.URI;
import java.net.URL;
import java.nio.file.StandardCopyOption;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author leifengyang
 * @version 1.0
 * @date 2025/9/13 10:22
 * @description:
 */
@Slf4j
@Service
public class TencentVodServiceImpl implements TencentVodService {

    @Autowired
    VodClient vodClient;

    @Autowired
    VodUploadClient vodUploadClient;

    @Autowired
    DramasMapper dramasMapper;

    @Autowired
    EpisodesMapper episodesMapper;

    @Autowired
    VodProperties vodProperties;

    @Autowired
    XxlHttp xxlHttp;

    @Override
    public void vodTransalator(Long dramaId) {
        //TODO 启动两个转码任务，让自动运行。xxl-job调度。
        HttpCookie cookie = xxlHttp.robotLogin();


        //先触发信息流任务
        Map<String,Object> map = new HashMap<>();
        map.put("id","4");
        map.put("executorParam",dramaId);
        map.put("addressList","");
        xxlHttp.triggerJob(cookie,map);


        //在触发画质任务
        Map<String,Object> qualityMap = new HashMap<>();
        qualityMap.put("id","5");
        qualityMap.put("executorParam",dramaId);
        qualityMap.put("addressList","");
        xxlHttp.triggerJob(cookie,qualityMap);
    }

    /**
     * 信息流转化
     * @param dramaId
     */
    @Override
    public void vodInfoFlowTransalator(Long dramaId) {
        //1、获取短剧 & 所有剧集
        Dramas dramas = dramasMapper.selectById(dramaId);
        //TODO 处理预告片信息流
        String trailerUrl = dramas.getTrailerUrl();
        if (StringUtils.isNotEmpty(trailerUrl)) {
            //短剧有自己预告片：把预告片上传并解码，得到返回结果
            uploadAndTranslateInfoFlowReturnResult(dramaId,trailerUrl);
        }else {
            //短剧自己没有上传预告片。就来找所有的剧集
            LambdaQueryWrapper<Episodes> eq = Wrappers.lambdaQuery(Episodes.class)
                .eq(Episodes::getDramaId, dramaId);
            List<Episodes> episodes = episodesMapper.selectList(eq);

            // 从剧集拿到所有预告片
            // 1、都是预告片
            // 2、都不是：默认用第一集
            Episodes firstEpiscode = episodes.stream()
                .filter(ele -> ele.getIsTrailer().equals(1L))
                .findFirst()  //拿到第一个预告片；
                .orElse(episodes.get(0));

            String videoUrl = firstEpiscode.getVideoUrl();
            //上传并解码，拿到结果。更新数据库
            uploadAndTranslateInfoFlowReturnResult(dramaId,videoUrl);
        }
    }

    //画质转码
    @Override
    public void vodQualityTransalator(Long dramaId) {
        //1、获取到所有的剧集
        LambdaQueryWrapper<Episodes> eq = Wrappers
            .lambdaQuery(Episodes.class)
            .eq(Episodes::getDramaId, dramaId);
        //2、获取所有剧集在minio中的url地址
        List<Episodes> episodes = episodesMapper.selectList(eq);

        //3、所有短剧上传到腾讯云，并启动画质流转码
        List<EpisodeVodRelation> list = episodes.stream()
            .map(epi -> {
                EpisodeVodRelation relation = new EpisodeVodRelation();
                //设置剧集id
                relation.setEpiId(epi.getEpisodeId());
                //TODO 1上传并转换画质
                String fileId = uploadVodAndTranslate(epi.getVideoUrl(), VodConst.QUALITY_FLOW_PROCESS);
                //2. 每个短剧上传到腾讯云后会有一个 fileId
                relation.setFileId(fileId);
                return relation;
            }).toList();

        //4、获取所有的转码结果
        for (EpisodeVodRelation relation : list) {
            EpisodeVodRelation waited = waitQualityTranslateResult(relation.getFileId());
            //如果没有拿到结果，继续重试； 由于一定会返回对象，必须拿画质的url判断
            /**
             * 第一次失败（判断写反了）： != 判断逻辑问题。  我们是没有拿到结果，继续重试；
             * 第二次失败（判断写错误）： 总是返回对象，一定有值，要用对象中的相关属性判断是否封装成功！
             */
            while (StringUtils.isEmpty(waited.getLowUrl())) {
                try {
                    Thread.sleep(4000);
                } catch (InterruptedException e) {
                    log.error("等待转码结果失败",e);
                }
                waited = waitQualityTranslateResult(relation.getFileId());
            }

            //来到这儿说明等到结果了
            relation.setLowUrl(waited.getLowUrl());
            relation.setHdUrl(waited.getHdUrl());
            relation.setSdUrl(waited.getSdUrl());
        }

        //4、走到这儿；数据库保存 剧集和清晰度关系
        for (EpisodeVodRelation relation : list) {
            System.out.println("即将更新的数据..."+relation);
            LambdaUpdateWrapper<Episodes> wrapper = Wrappers.lambdaUpdate(Episodes.class)
                .set(Episodes::getVideoUrlHd, relation.getHdUrl())
                .set(Episodes::getVideoUrlSd, relation.getSdUrl())
                .set(Episodes::getVideoUrlLow, relation.getLowUrl())
                .eq(Episodes::getEpisodeId, relation.getEpiId());
            episodesMapper.update(wrapper);
            log.info("剧集：{}, 清晰度更新完成",relation.getEpiId());
        }


    }


    /**
     * 1. 全平台临时文件兼容性； Java提供任何平台准备临时目录，创建文件
     * 2. 可靠性的事件消费：做完的事情，保存事件；主动拉取事件。判断是自己的事件。回复确认
     * 3. 阻塞等待结果返回（无限次稍后重试）：判断事件是否 FINISH，才能获取结果。
     * @param dramaId
     * @param trailerUrl
     * @return
     */
    @NotNull
    private String uploadAndTranslateInfoFlowReturnResult(Long dramaId,String trailerUrl) {
        //短剧本身就有预告片
        //1.1 上传到 vod 并转码
        String fileId = uploadVodAndTranslate(trailerUrl,VodConst.INFO_FLOW_PROCEDURE);
        //1.2 获取转码结果
        String result = waitInfoFlowTranslateResult(fileId);
        //循环等结果。因为转码比较慢
        while (result == null){
            try { //修整一段时间，继续拉事件
                Thread.sleep(4000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            result = waitInfoFlowTranslateResult(fileId);
        }
        LambdaUpdateWrapper<Dramas> wrapper = Wrappers.lambdaUpdate(Dramas.class)
            .set(Dramas::getTrailerInfoflowUrl, result)
            .eq(Dramas::getDramaId, dramaId);
        dramasMapper.update(wrapper);
        return result;
    }

    /**
     * 上传并转换信息流
     * @param url
     * @param flowName 需要启动的任务流名字
     */
    private String uploadVodAndTranslate(String url, String flowName) {
        //1、根据url地址进行上传
        File file = downloadAndSaveTemp(url, url.split("_")[1]);
        //2、构造 上传视频 请求
        VodUploadRequest request = new VodUploadRequest();
        //磁盘路径
        request.setMediaFilePath(file.getAbsolutePath());
        //指定应用
        request.setSubAppId(vodProperties.getStuAppId());
        request.setProcedure(flowName);

        //3、上传并转码
        String fileId = null;
        try {
            VodUploadResponse response = vodUploadClient.upload(vodProperties.getRegion(), request);
            fileId = response.getFileId();

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return  fileId;
    }


    /**
     * ["aaaa","aaaadd","fdfgfff"]
     * @return
     */
    private EpisodeVodRelation waitQualityTranslateResult(String fileId){
        EpisodeVodRelation relation = new EpisodeVodRelation();
        //等待转码结果
        PullEventsRequest request = new PullEventsRequest();
        request.setSubAppId(vodProperties.getStuAppId());


        try {
            PullEventsResponse response = vodClient.PullEvents(request);
            if (response != null && response.getEventSet() != null) {
                log.info("获取到腾讯云事件....{}",response);
                for (EventContent content : response.getEventSet()) {
                    log.info("事件类型：{}",content.getEventType());
                    //如果是转码事件，且是当前文件的
                    if (content.getEventType().equals(VodConst.PROCEDURE_STATE_CHANGED)) {
                        //拿到了我的事件
                        ProcedureTask event = content.getProcedureStateChangeEvent();
                        log.info("事件详情 fileId：{}",event.getFileId());
                        //拿到自己的转码数据
                        if (event.getFileId().equals(fileId) && event.getStatus().equals(VodConst.STATUS_FINISH)) {

                            log.info("获取到自己的转码数据，正在处理.... {}",event.getFileId());
                            ConfirmEventsRequest confirmReuest = new ConfirmEventsRequest();
                            confirmReuest.setSubAppId(vodProperties.getStuAppId());
                            confirmReuest.setEventHandles(new String[]{content.getEventHandle()});


                            //TODO 获取到数据; 封装转码后的结果
                            for (MediaProcessTaskResult result : event.getMediaProcessResultSet()) {
                                MediaTranscodeItem output = result.getTranscodeTask().getOutput();
                                String url = output.getUrl();
                                switch (output.getWidth()+""){
                                    case "1080":
                                        relation.setHdUrl(url);
                                        break;
                                    case "720":
                                        relation.setSdUrl(url);
                                        break;
                                    case "480":
                                        relation.setLowUrl(url);
                                        break;
                                }

                            }
                            XxlJobHelper.log("画质数据解析完成, {}",relation);
                            log.info("画质数据解析完成, {}",relation);
                            //确认事件已经收到，防止重复获取
                            vodClient.ConfirmEvents(confirmReuest);
                            //做完自己的事情跳出
                            return relation;
                        }
                    }
                }
            }
        } catch (Exception e) {
            log.error("等待转码结果失败",e);
            XxlJobHelper.log("等待转码结果失败: {}，稍后重试",e.getMessage());
        }
        return relation;
    }

    /**
     * 1. 等待时间短一点，及时响应
     * 2. 所有的转码异常（获取不到事件）都不要抛出；
     * @param fileId
     * @return
     */
    private String waitInfoFlowTranslateResult(String fileId) {
        String result = null;
        //等待转码结果
        PullEventsRequest request = new PullEventsRequest();
        request.setSubAppId(vodProperties.getStuAppId());


        try {
            PullEventsResponse response = vodClient.PullEvents(request);
            if (response != null && response.getEventSet() != null) {
                log.info("获取到腾讯云事件....{}",response);
                for (EventContent content : response.getEventSet()) {
                    //如果是转码事件，且是当前文件的
                    if (content.getEventType().equals(VodConst.PROCEDURE_STATE_CHANGED)) {
                        //拿到了我的事件
                        ProcedureTask event = content.getProcedureStateChangeEvent();
                        //拿到自己的转码数据
                        if (event.getFileId().equals(fileId) && event.getStatus().equals(VodConst.STATUS_FINISH)) {

                            ConfirmEventsRequest confirmReuest = new ConfirmEventsRequest();
                            confirmReuest.setSubAppId(vodProperties.getStuAppId());
                            confirmReuest.setEventHandles(new String[]{content.getEventHandle()});

                            //确认事件已经收到，防止重复获取
                            vodClient.ConfirmEvents(confirmReuest);
                            //TODO 获取到数据
                            MediaTranscodeItem output = event.getMediaProcessResultSet()[0]
                                .getTranscodeTask()
                                .getOutput();

                            result = output.getUrl();
                            return result;
                        }
                    }
                }
            }
        } catch (Exception e) {
            log.error("等待信息流转码结果失败,稍后重试...",e);
            XxlJobHelper.log("等待转码结果失败: {}，稍后重试",e.getMessage());
        }
        return result;
    }


    /**
     * 下载保存数据
     *
     * @param url
     * @param fileName
     * @return
     */
    private File downloadAndSaveTemp(String url, String fileName) {
        //下载来保存到一个临时文件中
        URL netUrl = null;
        File tempFile = null;
        try {
            //得到网络地址
            netUrl = URI.create(url).toURL();
            try (InputStream inputStream = netUrl.openStream();) {
                //创建一个临时位置
                tempFile = File.createTempFile("dat", "_" + fileName);
                System.out.println(tempFile.getAbsolutePath());
                FileUtil.copyFile(inputStream, tempFile, StandardCopyOption.REPLACE_EXISTING);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        return tempFile;
    }

}
